package main

import (
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"path/filepath"

	"github.com/Jeffail/benthos/v3/internal/bundle"
	"github.com/Jeffail/benthos/v3/internal/docs"
	"github.com/Jeffail/benthos/v3/internal/template"
	"github.com/Jeffail/benthos/v3/lib/config"
	"github.com/Jeffail/benthos/v3/lib/input"
	"github.com/Jeffail/benthos/v3/lib/output"
	"github.com/Jeffail/benthos/v3/lib/processor"
	yaml "gopkg.in/yaml.v3"

	_ "github.com/Jeffail/benthos/v3/public/components/all"
)

//------------------------------------------------------------------------------

var verbose bool

func create(t, path string, resBytes []byte) {
	if existing, err := ioutil.ReadFile(path); err == nil {
		if bytes.Equal(existing, resBytes) {
			if verbose {
				fmt.Printf("Skipping '%v' at: %v\n", t, path)
			}
			return
		}
	}
	if err := ioutil.WriteFile(path, resBytes, 0644); err != nil {
		panic(err)
	}
	fmt.Printf("Configuration for '%v' has changed, updating: %v\n", t, path)
}

func createYAML(t, path string, disableLint bool, sanit interface{}) {
	resBytes := []byte("# This file was auto generated by benthos_config_gen.\n")
	if disableLint {
		resBytes = append([]byte("# BENTHOS LINT DISABLE\n"), resBytes...)
	}

	var cBytes bytes.Buffer
	enc := yaml.NewEncoder(&cBytes)
	enc.SetIndent(2)
	if err := enc.Encode(sanit); err != nil {
		panic(err)
	}
	resBytes = append(resBytes, cBytes.Bytes()...)

	if existing, err := ioutil.ReadFile(path); err == nil {
		if bytes.Equal(existing, resBytes) {
			if verbose {
				fmt.Printf("Skipping '%v' at: %v\n", t, path)
			}
			return
		}
	}
	if err := ioutil.WriteFile(path, resBytes, 0644); err != nil {
		panic(err)
	}
	fmt.Printf("Configuration for '%v' has changed, updating: %v\n", t, path)
}

func main() {
	configsDir := "./config"
	flag.StringVar(&configsDir, "dir", configsDir, "The directory to write config examples")
	flag.BoolVar(&verbose, "v", false, "Writes more information to stdout, including configs that aren't updated")
	flag.Parse()

	if _, err := template.InitTemplates(); err != nil {
		panic(err)
	}

	// Get list of all types (both input and output).
	typeMap := map[string]struct{}{}
	for _, info := range bundle.AllInputs.Docs() {
		if info.Status != docs.StatusDeprecated && info.Status != docs.StatusExperimental {
			typeMap[info.Name] = struct{}{}
		}
	}
	for _, info := range bundle.AllOutputs.Docs() {
		if info.Status != docs.StatusDeprecated && info.Status != docs.StatusExperimental {
			typeMap[info.Name] = struct{}{}
		}
	}

	// Generate configs for all types.
	for t := range typeMap {
		conf := config.New()
		conf.Input.Processors = nil
		conf.Output.Processors = nil
		conf.Pipeline.Processors = nil

		if _, exists := input.Constructors[t]; exists {
			conf.Input.Type = t
		}
		if _, exists := output.Constructors[t]; exists {
			conf.Output.Type = t
		}

		var rawNode yaml.Node
		if err := rawNode.Encode(&conf); err != nil {
			panic(err)
		}

		if err := config.Spec().SanitiseYAML(&rawNode, docs.SanitiseConfig{
			RemoveTypeField:  true,
			RemoveDeprecated: true,
			ForExample:       true,
		}); err != nil {
			panic(err)
		}

		createYAML(t, filepath.Join(configsDir, t+".yaml"), false, rawNode)
	}

	// Create processor configs for all types.
	for _, info := range bundle.AllProcessors.Docs() {
		if info.Status == docs.StatusDeprecated || info.Status == docs.StatusExperimental {
			continue
		}

		conf := config.New()
		conf.Input.Processors = nil
		conf.Output.Processors = nil

		procConf := processor.NewConfig()
		procConf.Type = info.Name

		conf.Pipeline.Processors = append(conf.Pipeline.Processors, procConf)

		var rawNode yaml.Node
		if err := rawNode.Encode(&conf); err != nil {
			panic(err)
		}

		if err := config.Spec().SanitiseYAML(&rawNode, docs.SanitiseConfig{
			RemoveTypeField:  true,
			RemoveDeprecated: true,
			ForExample:       true,
		}); err != nil {
			panic(err)
		}

		createYAML(info.Name, filepath.Join(configsDir, "processors", info.Name+".yaml"), false, rawNode)
	}

	// Create metrics configs for all types.
	for _, info := range bundle.AllMetrics.Docs() {
		if info.Status == docs.StatusDeprecated || info.Status == docs.StatusExperimental {
			continue
		}

		conf := config.New()
		conf.Input.Processors = nil
		conf.Output.Processors = nil
		conf.Pipeline.Processors = nil

		conf.Metrics.Type = info.Name

		var rawNode yaml.Node
		if err := rawNode.Encode(&conf); err != nil {
			panic(err)
		}

		if err := config.Spec().SanitiseYAML(&rawNode, docs.SanitiseConfig{
			RemoveTypeField:  true,
			RemoveDeprecated: true,
			ForExample:       true,
		}); err != nil {
			panic(err)
		}

		createYAML(info.Name, filepath.Join(configsDir, "metrics", info.Name+".yaml"), false, rawNode)
	}

	// Create tracer configs for all types.
	for _, info := range bundle.AllTracers.Docs() {
		if info.Status == docs.StatusDeprecated || info.Status == docs.StatusExperimental {
			continue
		}

		conf := config.New()
		conf.Input.Processors = nil
		conf.Output.Processors = nil
		conf.Pipeline.Processors = nil

		conf.Tracer.Type = info.Name

		var rawNode yaml.Node
		if err := rawNode.Encode(&conf); err != nil {
			panic(err)
		}

		if err := config.Spec().SanitiseYAML(&rawNode, docs.SanitiseConfig{
			RemoveTypeField:  true,
			RemoveDeprecated: true,
			ForExample:       true,
		}); err != nil {
			panic(err)
		}

		createYAML(info.Name, filepath.Join(configsDir, "tracers", info.Name+".yaml"), false, rawNode)
	}
}

//------------------------------------------------------------------------------
